#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/kernel/processmgr.h>

#include "activity/activities.h"
#include "list/cheat_list.h"
#include "setting/setting.h"
#include "emu/emu.h"
#include "config.h"
#include "utils.h"

enum cheat_handler_type
{
    CHEAT_HANDLER_TYPE_EMU = 0,
    CHEAT_HANDLER_TYPE_RETRO
};

static struct retro_memory_map cheat_memory_maps = {0};
static SceKernelLwMutexWork cheat_mutex = {0};
static SceUID cheat_thid = -1;
static int cheat_okay = 0;
static int cheat_run = 0;
static int cheat_pause = 1;
static int cheat_reset = 0;
static int cheat_wait = 0;

void Emu_PauseCheat()
{
    if (Emu_IsGameExiting())
    {
        sceKernelLockLwMutex(&cheat_mutex, 1, NULL);
        cheat_pause = 1;
        sceKernelUnlockLwMutex(&cheat_mutex, 1);
    }
    else
    {
        cheat_pause = 1;
    }
}

void Emu_ResumeCheat()
{
    cheat_pause = 0;
}

int Emu_CleanCheatOption()
{
    Setting_SetCheatMenu(NULL);
    LinkedListDestroy(core_cheat_list);
    core_cheat_list = NULL;

    return 0;
}

int Emu_UpdateCheatOption()
{
    cheat_reset = 1;
    CheatListUpdateConfig(core_cheat_list);

    return 0;
}

int Emu_ResetCheatOption()
{
    return CheatListResetConfig(core_cheat_list);
}

int Emu_LoadCheatOption()
{
    char path[1024];

    if (!core_cheat_list)
    {
        core_cheat_list = NewCheatList();
        if (!core_cheat_list)
            return -1;
    }

    MakeCheatPath(path);
    if (CheatListGetEntries(core_cheat_list, path) < 0)
    {
        MakeCheatPath2(path);
        CheatListGetEntries(core_cheat_list, path);
    }
    if (LinkedListGetLength(core_cheat_list) <= 0)
        return -1;

    MakeConfigPath(path, CHEAT_CONFIG_NAME, TYPE_CONFIG_GAME);
    CheatListLoadConfig(core_cheat_list, path);
    Setting_SetCheatMenu(core_cheat_list);

    return 0;
}

int Emu_SaveCheatOption()
{
    if (!core_cheat_list)
        return -1;

    char path[1024];
    MakeConfigPath(path, CHEAT_CONFIG_NAME, TYPE_CONFIG_GAME);
    return CheatListSaveConfig(core_cheat_list, path);
}

static void cleanCheatMemoryMaps()
{
    if (cheat_memory_maps.descriptors)
    {
        free((void *)cheat_memory_maps.descriptors);
        cheat_memory_maps.descriptors = NULL;
    }
    cheat_memory_maps.num_descriptors = 0;
}

static int createCheatMemoryMaps()
{
    cleanCheatMemoryMaps();

    void *memory_data = retro_get_memory_data(RETRO_MEMORY_SYSTEM_RAM);
    size_t memory_size = retro_get_memory_size(RETRO_MEMORY_SYSTEM_RAM);
    if (!memory_data || memory_size == 0)
        return -1;

    struct retro_memory_descriptor *descriptors = calloc(1, sizeof(struct retro_memory_descriptor));
    if (!descriptors)
        return -1;

    descriptors->ptr = memory_data;
    descriptors->start = 0;
    descriptors->len = memory_size;

    cheat_memory_maps.descriptors = descriptors;
    cheat_memory_maps.num_descriptors = 1;

    return 0;
}

static void *translateAddressFromMemroyMaps(uint32_t address, const struct retro_memory_map *mmaps)
{
    if (mmaps && mmaps->descriptors)
    {
        int i;
        for (i = 0; i < mmaps->num_descriptors; i++)
        {
            if ((address >= mmaps->descriptors[i].start) && address < mmaps->descriptors[i].start + mmaps->descriptors[i].len)
                return mmaps->descriptors[i].ptr + mmaps->descriptors[i].offset + (address - mmaps->descriptors[i].start);
        }
    }

    return NULL;
}

static int translateAddress(uint32_t address, uint8_t **curr)
{
    *curr = translateAddressFromMemroyMaps(address, &core_memory_maps);
    if (!*curr)
        *curr = translateAddressFromMemroyMaps(address, &cheat_memory_maps);
    if (!*curr)
        return -1;

    return 0;
}

static void applyValue(uint8_t *curr, int big_endian, uint32_t value_to_set, int bytes_per_item, uint32_t value_mask)
{
    switch (bytes_per_item)
    {
    case 2:
        if (big_endian)
        {
            curr[0] &= ~((value_mask >> 8) & 0xFF);
            curr[1] &= ~(value_mask & 0xFF);

            curr[0] |= (value_to_set >> 8) & 0xFF;
            curr[1] |= value_to_set & 0xFF;
        }
        else
        {
            curr[0] &= ~(value_mask & 0xFF);
            curr[1] &= ~((value_mask >> 8) & 0xFF);

            curr[0] |= value_to_set & 0xFF;
            curr[1] |= (value_to_set >> 8) & 0xFF;
        }
        break;

    case 4:
        if (big_endian)
        {
            curr[0] &= ~((value_mask >> 24) & 0xFF);
            curr[1] &= ~((value_mask >> 16) & 0xFF);
            curr[2] &= ~((value_mask >> 8) & 0xFF);
            curr[3] &= ~(value_mask & 0xFF);

            curr[0] |= (value_to_set >> 24) & 0xFF;
            curr[1] |= (value_to_set >> 16) & 0xFF;
            curr[2] |= (value_to_set >> 8) & 0xFF;
            curr[3] |= value_to_set & 0xFF;
        }
        else
        {
            curr[0] &= ~(value_mask & 0xFF);
            curr[1] &= ~((value_mask >> 8) & 0xFF);
            curr[2] &= ~((value_mask >> 16) & 0xFF);
            curr[3] &= ~((value_mask >> 24) & 0xFF);

            curr[0] = value_to_set & 0xFF;
            curr[1] = (value_to_set >> 8) & 0xFF;
            curr[2] = (value_to_set >> 16) & 0xFF;
            curr[3] = (value_to_set >> 24) & 0xFF;
        }
        break;

    case 1:
    default:
        curr[0] &= ~(value_mask & 0xFF);
        curr[0] |= (value_to_set & 0xFF);
        break;
    }
}

static int applyCheat(CheatEntryData *data, int index)
{
    int i;
    CheatValue *value;
    CheatCode *code;
    uint8_t *curr;

    if (!data->values) // 没有选项
        return 0;

    value = &data->values[data->select];

    if (value->max_times != 0 && data->cur_times >= value->max_times)
        return 0;

    // 核心金手指
    if (data->handler == CHEAT_HANDLER_TYPE_EMU && value->native)
    {
        retro_cheat_set(index, 1, value->native);
        return 0;
    }

    // 前端金手指
    if (!value->codes) // 没有代码
        return 0;

    if (!cheat_memory_maps.descriptors && createCheatMemoryMaps() < 0) // 获取内存映射失败
        return -1;

    for (i = 0; i < value->n_codes; i++)
    {
        code = &value->codes[i];
        if (translateAddress(code->address, &curr) < 0)
            return -1;

        applyValue(curr, data->endian, code->value, code->bytes, code->mask);
    }

    if (value->max_times != 0 && data->cur_times < value->max_times)
        data->cur_times++;

    return 0;
}

int Emu_ApplyCheatOption()
{
    if (cheat_reset)
    {
        retro_cheat_reset();
        cheat_reset = 0;
    }

    if (!core_cheat_list || LinkedListGetLength(core_cheat_list) <= 0)
        return -1;

    LinkedListEntry *entry = LinkedListHead(core_cheat_list);
    int index = 0;

    while (entry)
    {
        CheatEntryData *data = (CheatEntryData *)LinkedListGetEntryData(entry);
        applyCheat(data, index);
        entry = LinkedListNext(entry);
        index++;
    }

    return 0;
}

static int cheatThreadEntry(SceSize args, void *argp)
{
    APP_LOG("[CHEAT] Cheat thread start run.\n");
    uint64_t cur_micros, next_micros;

    while (cheat_run)
    {
        sceKernelLockLwMutex(&cheat_mutex, 1, NULL);

        if (cheat_pause || LinkedListGetLength(core_cheat_list) <= 0)
        {
            sceKernelUnlockLwMutex(&cheat_mutex, 1);
            sceKernelDelayThread(1000);
            continue;
        }

        next_micros = sceKernelGetProcessTimeWide() + Emu_GetMicrosPerFrame();

        Emu_LockRunGameMutex();
        if (cheat_wait > 0)
            --cheat_wait;
        else
            Emu_ApplyCheatOption();
        Emu_UnlockRunGameMutex();

        sceKernelUnlockLwMutex(&cheat_mutex, 1);

        cur_micros = sceKernelGetProcessTimeWide();
        if (cur_micros < next_micros)
            sceKernelDelayThread(next_micros - cur_micros);
    }

    APP_LOG("[CHEAT] Cheat thread stop run.\n");
    sceKernelExitThread(0);
    return 0;
}

static int startCheatThread()
{
    int ret = 0;

    if (cheat_thid < 0)
        ret = cheat_thid = sceKernelCreateThread("emu_cheat_thread", cheatThreadEntry, 0x10000100, 0x40000, 0, 0, NULL);
    if (cheat_thid >= 0)
    {
        cheat_run = 1;
        ret = sceKernelStartThread(cheat_thid, 0, NULL);
        if (ret < 0)
        {
            cheat_run = 0;
            sceKernelDeleteThread(cheat_thid);
            cheat_thid = -1;
        }
    }

    return ret;
}

static int finishCheatThread()
{
    cheat_run = 0;
    if (cheat_thid >= 0)
    {
        sceKernelWaitThreadEnd(cheat_thid, NULL, NULL);
        sceKernelDeleteThread(cheat_thid);
        cheat_thid = -1;
    }

    return 0;
}

int Emu_SetCheatWait(int frames)
{
    cheat_wait = frames;

    return 0;
}

int Emu_InitCheat()
{
    if (cheat_okay)
        Emu_DeinitCheat();

    APP_LOG("[CHEAT] Cheat init...\n");

    if (Emu_LoadCheatOption() < 0)
        goto FAILED;

    createCheatMemoryMaps();

    sceKernelCreateLwMutex(&cheat_mutex, "emu_cheat_mutex", 2, 0, NULL);

    cheat_pause = 1;
    if (startCheatThread() < 0)
        goto FAILED_DEINIT;

    cheat_okay = 1;
    APP_LOG("[CHEAT] Cheat init OK!\n");
    return 0;

FAILED_DEINIT:
    Emu_DeinitCheat();
FAILED:
    APP_LOG("[CHEAT] Cheat init failed!\n");
    return -1;
}

int Emu_DeinitCheat()
{
    APP_LOG("[CHEAT] Cheat deinit...\n");

    cheat_okay = 0;
    finishCheatThread();
    sceKernelDeleteLwMutex(&cheat_mutex);
    Emu_CleanCheatOption();
    cleanCheatMemoryMaps();

    APP_LOG("[CHEAT] Cheat deinit OK!\n");
    return 0;
}
